package com.geekhole.demo.user.application.v3;

import com.geekhole.demo.user.domain.Agg;
import com.geekhole.demo.user.domain.DomainServiceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.repository.CrudRepository;

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public final class ApplicationServiceTemplate<AGG extends Agg> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationServiceTemplate.class);

    private final ApplicationEventPublisher eventPublisher;

    private final CrudRepository<AGG, ?> repository;

    public ApplicationServiceTemplate(ApplicationEventPublisher eventPublisher,
                                      CrudRepository<AGG, ?> repository) {
        this.eventPublisher = eventPublisher;
        this.repository = repository;
    }

    public <CONTEXT extends DomainServiceContext> void executeCreate(CONTEXT context,
                                                                     Function<CONTEXT, AGG> instanceFun,
                                                                     BiConsumer<CONTEXT, AGG> updateFun){
        try {
            // 1. 生成 聚合根
            AGG agg = instanceFun.apply(context);

            // 2. 执行业务访问
            updateFun.accept(context, agg);

            // 3. 保存聚合根
            save2DB(agg);

            publishAndCleanEvent(agg);

            onSuccess(agg, context);
        }catch (Exception e){
            onException(e, context);
        }
    }

    public <CONTEXT extends DomainServiceContext> void executeUpdate(CONTEXT context,
                                                                     Function<CONTEXT, AGG> loadFun,
                                                                     Consumer<CONTEXT> notFoundFun,
                                                                     BiConsumer<CONTEXT, AGG> updateFun){
        try {
            // 1. 加载聚合根
            AGG agg = loadFun.apply(context);

            // 2. 验证聚合根
            if (agg == null){
                notFoundFun.accept(context);
            }

            // 3. 调用聚合根方法
            updateFun.accept(context, agg);

            publishAndCleanEvent(agg);

            onSuccess(agg, context);
        }catch (Exception e){
            onException(e, context);
        }
    }

    private <CONTEXT extends DomainServiceContext> void onException(Exception e, CONTEXT context){
        LOGGER.error("failed to handle {}", context, e);
        if (e instanceof RuntimeException){
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    private <CONTEXT extends DomainServiceContext> void onSuccess(AGG agg, CONTEXT context){
        LOGGER.info("success to handle {} and sync {} to DB", context, agg);
    }

    private void publishAndCleanEvent(AGG agg){
        // 1. 发布领域事件
        agg.foreachEvent(this.eventPublisher::publishEvent);

        // 2. 清理领域事件
        agg.clearEvents();
    }

    private void save2DB(AGG agg){
        this.repository.save(agg);
    }
}
